package com.example.singleton;

import java.io.*;
import java.lang.reflect.Constructor;

/**
 * 线程安全，并非绝对线程安全，不能防止反射、序列化
 *
 * 线程安全的原因：
 * Java的乱序执行、初始化对象需要时间。对于语句3，JVM在执行时大致做了下述三件事：
 * a. 在内存中分配一块内存
 * b. 调用构造方法
 * c. 将内存的地址指向instance变量。（执行这一步后，instance != null）
 * volatitle 的一个特性：对变量的读取每次都是从主存中读取。
 * volatitle 另外一个特性：volatile 屏蔽指令重排序。也就是说，执行语句 3 时，肯定是按照abc的顺序执行的
 *
 * 如果按照abc的顺序执行也不会有什么问题。但由于Java乱序执行的机制，有可能在真实情况下执行顺序为acb。
 * 假设t1、t2是两个线程。t1执行到 1 时，发现为null，于是执行到语句 3，先执行a,再执行c,在还没有执行b时，
 * 时间片给了t2。这时，由于instance已经分配了地址空间，instance不为null了。所以t2在执行到语句 1 后直接return instance，
 * 获得了这个还没有被初始化的对象，然后在使用时就报错了
 *
 */
public class Singleton implements Serializable {

    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) { // 1
            synchronized (Singleton.class) {
                if (instance == null) {  // 2
                    instance = new Singleton(); // 3
                }
            }
        }
        return instance;
    }



    public static void main(String[] args) {

        try {
            // 常规方法实例化的对象
            Singleton s1 = Singleton.getInstance();
            System.out.println("单例获取:"+s1);


            // 使用反射破坏单例

            // 反射获取对象
            Class<Singleton> clazz = Singleton.class;
            // 得到无参构造方法
            Constructor cons = clazz.getDeclaredConstructor();
            // 把私有构造方法设为可访问 private失效
            cons.setAccessible(true);

//            // 如果有字段控制也无法防止反射生成对象
//            Field flag = clazz.getDeclaredField("flag");
//            flag.setAccessible(true);
//            flag.set(Boolean.class, false);

            // 利用构造方法生成对象
            Singleton s2 = (Singleton) cons.newInstance();
            System.out.println("反射获取:"+s2);
            System.out.println(s1 == s2);


            // 使用序列化破坏单例

            // 将生成的对象进行序列化
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(bos);
            outputStream.writeObject(s1);
            outputStream.flush();
            outputStream.close();

            // 将序列化的对象读取出来
            ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
            ObjectInputStream inputStream = new ObjectInputStream(bis);
            Singleton s3 = (Singleton)inputStream.readObject();
            inputStream.close();
            System.out.println("序列化获取:"+s3);
            System.out.println(s1 == s3);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}
